#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "devices/shutdown.h"
#include "lib/user/syscall.h"
#include "filesys/filesys.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"
#include "lib/string.h"
#include "filesys/file.h"
#include "userprog/process.h"
#include "userprog/pagedir.h"
#include "vm/frame.h"
#include "lib/round.h"
#include "threads/pte.h"

static struct openedFile * find_opened_file (int);
static void handle_halt (void);
static pid_t handle_exec (const char *);
static int handle_wait (pid_t);
static bool handle_create (const char *, unsigned);
static bool handle_remove (const char *);
static int handle_open (const char *);
static int handle_filesize (int);
static int handle_read (int, void *, unsigned);
static int handle_write (int fd, const char *, unsigned);
static void handle_seek (int, unsigned);
static unsigned handle_tell (int);
static void handle_close (int);
static mapid_t handle_mmap (int , void *);
static void handle_munmap (mapid_t);
/* The method returns true if the address is valid and can be dereferenced. 
This should be the first check, because if we dereference first, we need to 
handle page faults
does two checks
  1) if the address given is in user space
  2) if the page can be retrieved for the given esp pointer
*/
static bool
isAddressValid (void *esp)
{
  if (true == is_user_vaddr(esp))
    return true;
  else 
    return false;
}

/* Handler for halt system call, powers off the system */
static void
handle_halt (void)
{
  shutdown_power_off ();
}

/* Handler for exit system call, prints the status for the exiting thread.
   In addition, if a parent process is waiting on this process to finish, 
   stores the status of the exiting process into the parent thread and gives a 
   chance for the parent thread to run
*/
void
handle_exit (int status)
{
  printf ("%s: exit(%d)\n", thread_current ()->name, status);
  struct thread *cur = thread_current();
  struct thread *parent = cur->parent; 
  
  //update status so that the parent may track it
  updateChildStatus (cur, status);

  //close open files, if any and deallocate the resources
  if (-1 == status && 0 == list_size (&cur->fileOpenList))
    {      
      struct list_elem *e = NULL;
      struct openedFile *of = NULL;
      for (e = list_begin (&cur->fileOpenList); 
           e != list_end (&cur->fileOpenList);
           e = list_next (e))
        {
          of = list_entry (e, struct openedFile, fileElem);
          lock_acquire (&lock_for_file_operation);
          file_close (of->f);
          lock_release (&lock_for_file_operation);
          free(of);
        }
        
      //remove any children, since the thread is exiting, it does not
      //  need to keep track of its children anymore
      if(0 == list_size (&cur->childrenList))
        {
          struct child *c = NULL;
          for (e = list_begin (&cur->childrenList);
               e != list_end (&cur->childrenList);
               e = list_next (e))
            {
              c = list_entry (e, struct child, childelem);
              free (c);
            }
        }
    }
    
  //if there are entries in the map list, this means that they have not been
  //  unmapped
  if ( 0 != list_size (&cur->mapList))
    {
      struct mapStruct *m = NULL;
      struct list_elem *e;
      for (e = list_begin (&cur->mapList); 
           e != list_end (&cur->mapList);
           e = list_next (e))
        {
          //get the map struct object and call unmap on it passing the mapping
          // id
          m = list_entry (e, struct mapStruct, map_elem);
          handle_munmap (m->map_id);
        }
    }
      
  //if there is a parent waiting for this process to finish, signal the
  //  termination of this process
  if (parent->waiting_for_childid == thread_current ()->tid)
    {
      //printf ("parent %s would not be waiting for %d anymore\n", parent->name, parent->waiting_for_childid);
      //parent->waiting_for_childid = -1;
      parent->childExitReturnCode = status;
      sema_up (&parent->waitStatus);
    }
  thread_exit();
}

/* Handler for exec system call. Tries to load the command mentioned.
   returns -1 if the process could not be loaded otherwise returns the
   process id
*/
static pid_t
handle_exec (const char* command)
{
  if (false == isAddressValid (command))
    handle_exit (-1);
  
  //process_execute does the actual job of loading the command
  //process_execute returns tid_t
  pid_t processId = (pid_t) process_execute (command);

  //check if valid process id can be returned  
  if (-1 == processId) 
    return -1;

  //give the child process a chance to load
  sema_down (&thread_current ()->loadStatus);
  if (true == thread_current ()->loadReturnCode)
    return processId;
  else
    return -1;
}

/* Handler for wait system call. Uses the internal semantics of process_wait
     to achieve wait
*/
static int
handle_wait (pid_t pid)
{
  //printf ("current thread is %s and waiting for pid %d\n", thread_current (), pid);
  return process_wait (pid);
}

/* Handler for create system call. Returns true if file creation was 
     successful, false otherwise.
*/
static bool
handle_create (const char *fileName, unsigned size)
{
  int returnValue;
  //return if the filename passed is invalid
  if ((false == isAddressValid(fileName)) || (0 == strlen (fileName)))
      handle_exit(-1);
  
  lock_acquire (&lock_for_file_operation);
  returnValue = filesys_create (fileName, size);
  lock_release (&lock_for_file_operation);
  return returnValue;
}

/* Handler for remove system call. Returns true if the file could be removed,
     false otherwise.
*/
static bool
handle_remove (const char *fileName)
{
  int returnValue;
  //return if the filename passed is invalid
  if ((false == isAddressValid (fileName)) || (0 == strlen (fileName)))
    return false;
    
  lock_acquire (&lock_for_file_operation);
  returnValue = filesys_remove (fileName);
  lock_release (&lock_for_file_operation);
  return returnValue;
}

/* Handler for open system call. Returns -1 if the filename passed is null
     or if the file could not be opened for any reason.
     Stores the file pointer in a data structure and returns the corresponding
     file descriptor
*/
static int
handle_open (const char *fileName)
{
  //return if the filename passed is invalid  
  if ((NULL == fileName) || (0 == strlen (fileName)))
    return -1;
  
  //attempt to open the file
  struct file *f =  NULL;  
  lock_acquire (&lock_for_file_operation);
  f = filesys_open (fileName);
  lock_release (&lock_for_file_operation);
  
  if (NULL == f)
    return -1;
  
  //store the file pointer in a data structure and return the corresponding 
  //  file descriptor
  struct openedFile *of = NULL;
  of = (struct openedFile *)malloc (sizeof (struct openedFile));
  of->fd = thread_current ()->fileCount++;
  of->f = f;
  list_push_back (&thread_current ()->fileOpenList, &of->fileElem);
  return of->fd;
}

/* Handler for filesize system call. */
static int
handle_filesize (int fd)
{
  //given a file descriptor find the file pointer
  struct openedFile *of = find_opened_file (fd);
  
  //attempt a filesize operation only if the file descriptor is found!!
  int returnValue = -1;
  if (NULL != of)
    {
      int returnValue;
      lock_acquire (&lock_for_file_operation);
      returnValue = file_length (of->f);
      lock_release (&lock_for_file_operation);
      return returnValue;
    }
  return returnValue;
}

/* Handler for read system call.*/
static int
handle_read (int fd, void *buffer, unsigned buf_size)
{
  //if (!isAddressValid (buffer))
  if (!is_user_vaddr (buffer))
    handle_exit (-1);
  
  //check if we want to read from stdin
  if (0 == fd)
    {
      int i = 0;
      for (; i < buf_size; i++)
        { //read till the there is space in the buffer
          *(int8_t *) buffer = input_getc();
          buffer++;
        }
      return buf_size;
    }
    
  //given a file descriptor find the file pointer
  struct openedFile *of = find_opened_file (fd);
  
  int returnValue = -1;
  //attempt a read only if the file descriptor is found!!
  if (NULL != of)
    {
      int returnValue;
      lock_acquire (&lock_for_file_operation);
      returnValue = file_read (of->f, buffer, buf_size);
      lock_release (&lock_for_file_operation);
      return returnValue;
    }
  return returnValue;
}

/* Handler for write system call */
static int 
handle_write (int fd, const char *buf, unsigned buf_size)
{
  if (!isAddressValid (buf))
     handle_exit (-1);
  
  if (1 == fd) 
    { // we need to write to stdout
      putbuf(buf, buf_size);
      return buf_size;
    }
  
  //given a file descriptor find the file pointer
  struct openedFile *of = find_opened_file (fd);
 
  int returnValue = 0;   
  
  //write to the file only if a valid file handle is found
  if (NULL != of)
    {
      int returnValue;
      lock_acquire (&lock_for_file_operation);
      returnValue = file_write (of->f, (void *) buf, buf_size);
      lock_release (&lock_for_file_operation);
      return returnValue;
    }
  return returnValue;
}

/* Handler for seek file system call */
static void 
handle_seek (int fd, unsigned position)
{
  //given a fd, find the file pointer
  struct openedFile *of = find_opened_file (fd);
  
  //move the position of next read/write byte only if a valid file handle is
  //  found
  if (NULL != of)
    {      
      lock_acquire (&lock_for_file_operation);
      file_seek (of->f, position);
      lock_release (&lock_for_file_operation);
      return;
    }
  handle_exit (-1);  
}

/* Handler for tell system call */
static unsigned
handle_tell (int fd)
{
  //given a file descriptor, find the file pointer
  struct openedFile *of = find_opened_file (fd);
  
  int returnValue = -1;
  //return the position of the next read/write byte only if a valid file
  //  handle is found
  if (of != NULL)
    {
      int returnValue;
      lock_acquire (&lock_for_file_operation);
      returnValue = file_tell (of->f);
      lock_release (&lock_for_file_operation);
      return returnValue;
    }
  return returnValue;
}

/* Handler for close system call */
static void
handle_close (int fd)
{
  //given a file descriptor find the file pointer
  struct openedFile *of= find_opened_file (fd);
  
  //remove only if the file handle is found
  if (NULL != of)
    {
      lock_acquire (&lock_for_file_operation);
      list_remove (&of->fileElem);
      file_close (of->f);
      lock_release (&lock_for_file_operation);
      free (of);
      return;
    }
  handle_exit (-1);
}

static bool
mmap_address_check (void *addr)
{
  bool flag = true;
  //printf ("address received is %p\n", addr);
  if (!isAddressValid (addr) || (NULL == addr) || (NULL == *(int *) addr) || (0 == *(int *) addr))
    flag = false;
  return flag;
}

static bool 
mmap_invalid_fd_check (int fd)
{
  bool returnValue = false;
  if ((0==fd) || (1 == fd) || (2 == fd))
    returnValue = true;
    
  return returnValue;
}

static bool
overlap (int fd, void *addr)
{
  int filesize = handle_filesize (fd);
  //start from the base address and move till the end of the file and see if the overlap occurs
  //overlap occurs if both these conditions are satisfied
  //1) we get a page table entry in the page table
  //2) we get a page_struct for the corresponding page table entry in the supplemental page table 
  void *base_address = *(int *) addr;
  bool overlap = false;
  uint32_t *page_table_entry = NULL;
  struct page_struct *page_struct = NULL;
  while (base_address < *(int *)addr + filesize)
    {
      page_table_entry = supplementary_page_table_page_entry_find (thread_current ()->pagedir, base_address);
      
      if (NULL != page_table_entry)
        {
          page_struct = supplementary_page_table_page_struct_lookup (page_table_entry);
          if (NULL != page_struct)
            {
              overlap = true;
              break;
            }
        }
      base_address += PGSIZE;
    }
  //printf ("overlap status is %d\n", overlap);
  return overlap;
}
static mapid_t
handle_mmap (int fd, void *addr)
{
  void *base_address = (void *) *(int *) addr;
  struct thread *cur = thread_current ();
  struct openedFile *of= find_opened_file (fd);
  
  if (NULL == of)
  {
    //printf ("returning -1 for null of");
    return -1;
  }
    
  if ( !isAddressValid (addr) || !isAddressValid (base_address) || (0 == base_address))
  {
    //printf ("returning -1 for invalid address");
    return -1;
  }
  
  if ((0 == fd) || (1 == fd) || (2 == fd))
  {
    //printf ("returning -1 for invalid fd");
    return -1;
  }
  
  //get overlap status
  bool returnValueForOverlap = overlap (fd, addr);
  if (true == returnValueForOverlap)
    return -1; 
  
  mapid_t current_mapping_id = thread_current ()->next_mappingId;
  thread_current ()->next_mappingId += 1;
  
  struct mapStruct *map_struct = (struct mapStruct *) malloc (sizeof (struct mapStruct));
  if (NULL == map_struct)
    return -1;
    
  map_struct->map_id = current_mapping_id;
  map_struct->virtual_address = base_address;
  map_struct->f = file_reopen (of->f);
  list_push_back (&thread_current ()->mapList, &map_struct->map_elem);
  
  
  //we now map the file into the virtual memory
  uint32_t bytes_to_be_read = handle_filesize (fd);
  uint32_t bytes_to_be_zeroed = ROUND_UP (bytes_to_be_read, PGSIZE) - bytes_to_be_read;
//  if (bytes_to_be_read < PGSIZE)
//    bytes_to_be_zeroed = PGSIZE - bytes_to_be_read;
    
  void *user_page = base_address;
  //printf ("mapping address %p\n", user_page);
  int ofs = 0;
  struct file *f = map_struct->f;
  file_seek (f, ofs);
  
  while (bytes_to_be_read > 0 || bytes_to_be_zeroed > 0)
    {
      size_t page_read_bytes = bytes_to_be_read < PGSIZE ? bytes_to_be_read : PGSIZE;
      size_t page_zero_bytes = PGSIZE - page_read_bytes;
      
      //file_read (of->f, user_page, page_read_bytes);
      block_sector_t sector = byte_to_sector (file_get_inode (map_struct->f), ofs);
      if (!add_page (user_page, NULL, page_read_bytes, DISK, sector, true))
        return false;
        
      bytes_to_be_read -= page_read_bytes;
      bytes_to_be_zeroed -= page_zero_bytes;
      ofs += page_read_bytes;
      user_page += PGSIZE;
    }
  //printf ("\n returning %d\n", current_mapping_id);
  return current_mapping_id;
}

static void
handle_munmap (mapid_t mappingId)
{
  struct thread *cur = thread_current ();
  struct list_elem * e = NULL;
  struct mapStruct *map_struct = NULL;
  struct file * f = NULL;
  uint32_t file_size = 0;
  uint32_t bytes_to_be_written = 0;
  
  size_t bytes_written_for_page = 0;
  uint32_t *page_table_entry;
  void *user_page;
  
  for (e = list_begin (&cur->mapList); 
       e != list_end (&cur->mapList);
       e = list_next (e))
    {
      map_struct = list_entry (e, struct mapStruct, map_elem);
      if (map_struct->map_id == mappingId)
        {
          f = map_struct->f;
          file_size = file_length (f);
          bytes_to_be_written = file_size;
          user_page = map_struct->virtual_address;
          
          struct file *temp = file_open (file_get_inode (f));
          
          while ( 0 < bytes_to_be_written)
            {
              if (bytes_to_be_written < PGSIZE)
                bytes_written_for_page = bytes_to_be_written;
              else
                bytes_written_for_page = PGSIZE; 
                
              
              page_table_entry = supplementary_page_table_page_entry_find (cur->pagedir, user_page); 
              
              if (true == pagedir_is_dirty (cur->pagedir, user_page))
                {
                  file_write_at ( temp, user_page, bytes_written_for_page, (user_page - map_struct->virtual_address));
                }
                
              //now we delete the pte as it has been written
              uint32_t temp = *page_table_entry;
              supplementary_page_table_delete (page_table_entry);
              //  palloc_free_page (pte_get_page (temp));
                
              bytes_to_be_written -= bytes_written_for_page;
              user_page += PGSIZE;
            }
          //now do all the cleanup
          file_close (temp);
          list_remove (e);
          //free (map_struct);
          break;          
        }    
    }
  return;
}

/* Given a file descriptor, returns the opened file data structure 
     corresponding to the file descriptor, else returns NULL if the file
     descriptor is not found
*/
static struct openedFile *
find_opened_file (int fd)
{
  struct list_elem *e;
  struct thread *cur = thread_current ();
  struct openedFile *of = NULL;
  //move through each element in the list
  for (e = list_begin (&cur->fileOpenList); 
       e != list_end (&cur->fileOpenList);
       e = list_next (e))
    {
      of = list_entry (e, struct openedFile, fileElem);
      if (of->fd == fd)
          break;
    }
  return of;
}


static void syscall_handler (struct intr_frame *);

void
syscall_init (void) 
{
  intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); 
}

static void
syscall_handler (struct intr_frame *f UNUSED) 
{
  int syscall_value = -1;
  
  //before dereferencing esp, make sure it is valid
  if (false == isAddressValid (f->esp))
    handle_exit(-1);
      
  syscall_value = *((int *) (f->esp));
  thread_current ()->entered_syscall_handler = true;
  switch (syscall_value)
    {
      case SYS_HALT:
        handle_halt ();        
        break;
        
      case SYS_EXIT:
        //status for exit is at distance 4 from the esp
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        handle_exit (*(int *)(f->esp + 4));
        break;
                
      case SYS_EXEC:
        //command line for exec is at distance 4 from the esp
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        f->eax = handle_exec ((char *) (*((uint32_t *)(f->esp + 4))));
        break;
        
      case SYS_WAIT:
        //pid for wait is at distance 4 from the esp
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        f->eax = handle_wait (*(int *)(f->esp + 4));
        break;
        
      case SYS_CREATE:
        //initial size for creating the file is at distance 8 from esp
        //file name for creating the file is at distance 16 from esp
        if (false == isAddressValid (f->esp + 16) || 
            false == isAddressValid (f->esp + 8)) 
          handle_exit (-1);
        f->eax = handle_create ((char *) (*((uint32_t *)(f->esp + 16))), 
                                *(int *)(f->esp + 8));
        break;
      
      case SYS_REMOVE:
        //filename for removal is at distance 12 from esp
        if (false == isAddressValid (f->esp + 12))
          thread_exit ();
        f->eax = handle_remove ((char *) (*((uint32_t *)(f->esp + 12))));
        break;
        
      case SYS_OPEN:
        //filename required to be opened is at distance 4 from the esp
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        f->eax = handle_open ((char *) *((uint32_t *) (f->esp + 4)));
        break;
      
      case SYS_FILESIZE:
        //file whose size needs to be found out is at distance 4 from the esp
        if (false == isAddressValid (f->esp + 4))
            handle_exit (-1);
        f->eax = handle_filesize (*((int *) (f->esp +4)));;
        break;
        
      case SYS_READ:
        //file descriptor for file to be read is at distance 8 from esp
        //buffer into which contents should be put is at distance 24 from esp
        //number of bytes to be read from the file is at distance 28 from esp
        if (false == isAddressValid (f->esp + 8) ||
            false == isAddressValid (f->esp + 24) ||
            false == isAddressValid (f->esp + 4))
          handle_exit (-1);
      
        f->eax = handle_read (*((int *) (f->esp + 8)),
                              (char *) *((uint32_t *) (f->esp + 24)),
                              *((int *) (f->esp + 4)));
        break;
        
      case SYS_WRITE:
        //file descriptor for file to be written to is at distance 8 from esp
        //buf from which contents should be written is at distance 24 from esp
        //number of bytes to be written is at distance 28 from esp
        if (false == isAddressValid (f->esp + 8) ||
            false == isAddressValid (f->esp + 24) ||
            false == isAddressValid (f->esp + 28))
          handle_exit (-1);
            
        f->eax = handle_write (*((int *) (f->esp + 8)),
                               (char *) *((uint32_t *) (f->esp + 24)),
                               *((int *) (f->esp + 28)));
        break;
        
      case SYS_SEEK:
        //file descriptor on which seek should be made is at distance 8 from esp
        //position of next read/write should occur is at distance 16 from esp
        if (false == isAddressValid (f->esp + 8) || 
            false == isAddressValid (f->esp + 16))
          handle_exit (-1);
            
        handle_seek (*(int *)(f->esp + 16), *(int *)(f->esp + 8));
        break;
        
      case SYS_TELL:
        //file descriptor on which tell should occur is at distance 4 from esp
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        f->eax = handle_tell (*((int *) (f->esp +4)));;
        break;
        
      case SYS_CLOSE:
        //file descriptor on which close should occur is at distance 4 from esp
        if (false == isAddressValid (f->esp + 4))
            handle_exit (-1);
        handle_close (*((int *) (f->esp + 4)));        
        break;
        
      case SYS_MMAP:
        if ((false == isAddressValid (f->esp + 16)) || 
            (false == isAddressValid (f->esp + 8)))
          handle_exit (-1);
          
        uint32_t base = (uint32_t)*(int *)(f->esp + 8);
        //printf ("before check %p and value %p\n", base, *base);
        if (base % PGSIZE != 0)
            f->eax = -1;
        else
            f->eax = handle_mmap (*(int *)(f->esp + 16), (void *)(f->esp + 8));
        break;
        
      case SYS_MUNMAP:
        if (false == isAddressValid (f->esp + 4))
          handle_exit (-1);
        
        handle_munmap (*(int *)(f->esp + 4));
        break;
    }
    thread_current ()->entered_syscall_handler = false;
}
